﻿using System;
using System.Collections.Generic;
using System.Reflection;

namespace Borg
{
    public class TypeBuilder
    {
        internal TypeBuilder()
        { }

        public Repository CreateRepository(IList<ContractInfo> contractInfos,
            IList<PluginInfo> pluginInfos, DateTime? lastUpdate)
        {
            var repository = new Repository();

            foreach (var contractInfo in contractInfos)
                repository.contracts.Add(contractInfo.repositoryId, contractInfo);

            foreach (var pluginInfo in pluginInfos)
                repository.plugins.Add(pluginInfo.repositoryId, pluginInfo);

            repository.lastUpdate = lastUpdate.HasValue
                ? lastUpdate.Value
                : DateTime.Now;

            return repository;
        }

        public PluginInfo CreatePlugin(RepositoryId repositoryId,
            Assembly assembly, IList<ExtensionTypeInfo> extensionTypeInfos)
        {
            var pluginInfo = new PluginInfo(repositoryId, assembly);

            if (extensionTypeInfos == null)
                throw new Exception("Extension types must not be null.");

            foreach (var extensionTypeInfo in extensionTypeInfos)
            {
                if (extensionTypeInfo.pluginInfo != null)
                    throw new DuplicateAssignmentException("ExtensionType already assigned.");

                extensionTypeInfo.pluginInfo = pluginInfo;
            }

            pluginInfo.extensionTypeInfos =
                new RepositoryCollection<ExtensionTypeInfo>(extensionTypeInfos);

            return pluginInfo;
        }

        public ExtensionTypeInfo CreateExtensionType(ExtensionAttribute attribute, Type type,
            IList<PlugTypeInfo> plugTypes, IList<SlotTypeInfo> slotTypes)
        {
            return CreateExtensionType(attribute, type, plugTypes, slotTypes, null);
        }

        public ExtensionTypeInfo CreateExtensionType(ExtensionAttribute attribute,
            string typeName, IList<PlugTypeInfo> plugTypes, IList<SlotTypeInfo> slotTypes)
        {
            return CreateExtensionType(attribute, typeName, plugTypes, slotTypes, null);
        }

        public ExtensionTypeInfo CreateExtensionType(ExtensionAttribute attribute, string typeName,
            IList<PlugTypeInfo> plugTypes, IList<SlotTypeInfo> slotTypes, IList<MonitorInfo> monitors)
        {
            var extensionType = new ExtensionTypeInfo(null, attribute, typeName);
            InitExtensionType(extensionType, plugTypes, slotTypes, monitors);
            return extensionType;
        }

        public ExtensionTypeInfo CreateExtensionType(ExtensionAttribute attribute, Type type,
            IList<PlugTypeInfo> plugTypes, IList<SlotTypeInfo> slotTypes, IList<MonitorInfo> monitors)
        {
            var extensionType = new ExtensionTypeInfo(null, attribute, type);
            InitExtensionType(extensionType, plugTypes, slotTypes, monitors);
            return extensionType;
        }

        private static void InitExtensionType(ExtensionTypeInfo extensionType,
            IList<PlugTypeInfo> plugTypes, IList<SlotTypeInfo> slotTypes, IList<MonitorInfo> monitors)
        {
            if (plugTypes != null)
            {
                foreach (var plugTypeInfo in plugTypes)
                {
                    if (plugTypeInfo.extensionTypeInfo != null)
                        throw new DuplicateAssignmentException("PlugType already assigned.");

                    plugTypeInfo.extensionTypeInfo = extensionType;
                }
            }
            else
                plugTypes = new List<PlugTypeInfo>();

            extensionType.plugTypeInfos = new RepositoryCollection<PlugTypeInfo>(plugTypes);

            if (slotTypes != null)
            {
                foreach (SlotTypeInfo slotTypeInfo in slotTypes)
                {
                    if (slotTypeInfo.extensionTypeInfo != null)
                        throw new DuplicateAssignmentException("SlotType already assigned.");

                    slotTypeInfo.extensionTypeInfo = extensionType;
                }
            }
            else
                slotTypes = new List<SlotTypeInfo>();

            extensionType.slotTypeInfos = new RepositoryCollection<SlotTypeInfo>(slotTypes);

            if (monitors != null)
            {
                foreach (var monitorInfo in monitors)
                {
                    if (monitorInfo.extensionTypeInfo != null)
                        throw new DuplicateAssignmentException("Monitor already assigned.");

                    monitorInfo.extensionTypeInfo = extensionType;
                }
            }
            else
                monitors = new List<MonitorInfo>();

            extensionType.monitorInfos = new RepositoryCollection<MonitorInfo>(monitors);
        }

        public PlugTypeInfo CreatePlugType(PlugAttribute attribute, IList<ParamValue> paramValues)
        {
            var plugTypeInfo = new PlugTypeInfo(null, attribute);

            if (paramValues != null)
            {
                foreach (var paramValue in paramValues)
                {
                    if (paramValue.plugTypeInfo != null)
                        throw new DuplicateAssignmentException("ParamValue already assigned.");

                    paramValue.plugTypeInfo = plugTypeInfo;
                }
            }
            else
                paramValues = new List<ParamValue>();

            plugTypeInfo.paramValues = new RepositoryCollection<ParamValue>(paramValues);
            return plugTypeInfo;
        }

        public ParamValue CreateParamValue(ParamValueAttribute attribute)
        {
            return new ParamValue(null, attribute);
        }

        public ParamValue CreateParamValue(ParamValueAttribute attribute, bool defaultValue)
        {
            return new ParamValue(null, attribute, defaultValue);
        }

        public SlotTypeInfo CreateSlotType(SlotAttribute attribute)
        {
            return new SlotTypeInfo(null, attribute);
        }

        public MonitorInfo CreateMonitor(MonitorAttribute attribute)
        {
            return new MonitorInfo(null, attribute);
        }

        public ContractInfo CreateContract(RepositoryId repositoryId, Assembly assembly,
            IList<SlotDefinition> slotDefinitions)
        {
            var contractInfo = new ContractInfo(repositoryId, assembly);

            if (slotDefinitions != null)
            {
                foreach (SlotDefinition slotDefinition in slotDefinitions)
                {
                    if (slotDefinition.contractInfo != null)
                        throw new DuplicateAssignmentException("SlotDefinition already assigned.");

                    slotDefinition.contractInfo = contractInfo;
                }
            }
            else
                slotDefinitions = new List<SlotDefinition>();

            contractInfo.slotDefinitions = new RepositoryCollection<SlotDefinition>(slotDefinitions);
            return contractInfo;
        }

        public SlotDefinition CreateSlotDefinition(SlotDefinitionAttribute attribute,
            Type type, IList<ParamDefinition> paramDefinitions)
        {
            var slotDefinition = new SlotDefinition(null, attribute, type);
            InitSlotDefinition(slotDefinition, paramDefinitions);
            return slotDefinition;
        }

        public SlotDefinition CreateSlotDefinition(SlotDefinitionAttribute attribute,
            string typeName, IList<ParamDefinition> paramDefinitions)
        {
            var slotDefinition = new SlotDefinition(null, attribute, typeName);
            InitSlotDefinition(slotDefinition, paramDefinitions);
            return slotDefinition;
        }

        private static void InitSlotDefinition(SlotDefinition slotDefinition,
            IList<ParamDefinition> paramDefinitions)
        {
            if (paramDefinitions != null)
            {
                foreach (var paramDefinition in paramDefinitions)
                {
                    if (paramDefinition.slotDefinition != null)
                        throw new DuplicateAssignmentException("ParamDefinition already assigned.");

                    paramDefinition.slotDefinition = slotDefinition;
                }
            }
            else
                paramDefinitions = new List<ParamDefinition>();

            slotDefinition.paramDefinitions =
                new RepositoryCollection<ParamDefinition>(paramDefinitions);
        }

        public ParamDefinition CreateParamDefinition(ParamAttribute attribute)
        {
            return new ParamDefinition(null, attribute);
        }

        public PluginInfo Clone(PluginInfo plugin)
        {
            var etis = new List<ExtensionTypeInfo>();

            foreach (var eti in plugin.extensionTypeInfos)
            {
                var ptis = new List<PlugTypeInfo>();

                foreach (var pti in eti.plugTypeInfos)
                {
                    var pvs = new List<ParamValue>();

                    foreach (var pv in pti.paramValues)
                    {
                        pvs.Add(CreateParamValue(pv.paramValueAttribute));
                        --ParamValue.nextId;
                    }

                    ptis.Add(CreatePlugType(pti.plugAttribute, pvs));
                    --PlugTypeInfo.nextId;
                }

                var stis = new List<SlotTypeInfo>();

                foreach (var sti in eti.slotTypeInfos)
                {
                    stis.Add(CreateSlotType(sti.slotAttribute));
                    --SlotTypeInfo.nextId;
                }

                var mis = new List<MonitorInfo>();

                foreach (var mi in eti.monitorInfos)
                {
                    mis.Add(CreateMonitor(mi.monitorAttribute));
                    --MonitorInfo.nextId;
                }

                var extension = eti.type != (Type)null
                    ? CreateExtensionType(eti.extensionAttr, eti.type, ptis, stis, mis)
                    : CreateExtensionType(eti.extensionAttr, eti.typeName, ptis, stis, mis);

                --ExtensionTypeInfo.nextId;
                extension.typeName = eti.typeName;
                etis.Add(extension);
            }

            var pluginInfo = CreatePlugin(
                new RepositoryId(
                    plugin.repositoryId.Uri,
                    plugin.repositoryId.Name,
                    plugin.repositoryId.LastWriteTime),
                    plugin.assembly,
                    etis);

            pluginInfo.id = plugin.id;
            --PluginInfo.nextId;
            return pluginInfo;
        }

        public ContractInfo Clone(ContractInfo contract)
        {
            var sds = new List<SlotDefinition>();

            foreach (var sd in contract.slotDefinitions)
            {
                var pds = new List<ParamDefinition>();

                foreach (var pd in sd.paramDefinitions)
                {
                    pds.Add(CreateParamDefinition(pd.paramAttribute));
                    --ParamDefinition.nextId;
                }

                var slotDef = sd.type != (Type)null
                    ? CreateSlotDefinition(sd.slotDefinitionAttr, sd.type, pds)
                    : CreateSlotDefinition(sd.slotDefinitionAttr, sd.typeName, pds);

                --SlotDefinition.nextId;
                slotDef.typeName = sd.typeName;
                sds.Add(slotDef);
            }

            var contractInfo = CreateContract(
                new RepositoryId(
                    contract.repositoryId.Uri,
                    contract.repositoryId.Name,
                    contract.repositoryId.LastWriteTime),
                contract.assembly,
                sds);

            --ContractInfo.nextId;
            return contractInfo;
        }
    }
}

